#lang racket/gui
(require "server.abstract.class.rkt")

(define TICK (* 60 5 1000))
(define PORT 1121)
(define HOST "0.0.0.0")
(define COMPILED #f)

(debug #t)
(debug-port (current-output-port))

;; A tenent is a (tenent symbol boolean)
;; it represents one of the suites tenents and if they are currently in
(struct tenent (name [in? #:mutable]))

;; symbol -> tenent
;; makes a tenent who's in
(define (make-tenent name)
  (tenent name #t))

;; a connection is a (con number boolean)
;; represtents if the client is recorded as valid
(struct con (id valid?))

;; this class describes the basic server for tenent checkin
;; init:
;;      tenent (listof symbol) : names of tenents who live here
(define in-serv%
  (class* object% (effectful-server-module<%>)
    (init tenents)
    
    ;; easy way to reference all the names
    (define names tenents)
    (define conns null)
    ;; the actual tenents
    (define people 
      (for/list ([t tenents])
        (make-tenent t)))
    
    ;; accessor
    (define/public (get-names)
      names)
    
    ;; symbol -> Boolean
    ;; is this tenent in?
    (define/public (in? name)
      (define ten (findf (λ (t) (symbol=? name (tenent-name t))) people))
      (if ten (tenent-in? ten) #f))
    
    ;; name -> (void)
    ;; updates this tenent to that they have left/entered
    (define/public (port name)
      (define ten (findf (λ (t) (symbol=? name (tenent-name t))) people))
      (when (not (tenent-in? ten))
        (send this wake-up))
      (set-tenent-in?! ten (not (tenent-in? ten))))
    
    (define last-time 0)
    ;; time -> boolean
    ;; is it time to check?
    (define/public (is-time? t)
      (>= (- t last-time) (if (debug) 300000 TICK)))
    
    ;; are we in hybernate mode?
    (define hybernate? #f)
    ;; -> (void)
    ;; enter hybernate
    (define/public (hybernate)
      (set! hybernate? #t))
    ;; exit hybernate
    (define/public (wake-up)
      (when hybernate?
        (set! hybernate? #f)))
    
    ;; are we in a validation cycle?
    (define validate? #f)
    (define/public (validate)
      (set! validate? #t)
      (set! conns
            (for/list ([c conns])
              (con (con-id c) #f))))
    ;; -> boolean
    ;; have all the tenents left?
    (define/public (everyone-out?)
      (for/and ([ten people])
        (not (tenent-in? ten))))
    
    (define/public (on-tick time)
      (cond [(not (is-time? time))
             (package '() '())]
            [else
              (set! last-time time)
              (cond [(and (not hybernate?)
                          (everyone-out?))
                     (send this hybernate)
                     (package (list (message -1 'hibernate)) '())]
                    [validate?
                     (set! validate? #f)
                     (let ([dis (for/list ([c conns]
                                           #:when (not (con-valid? c)))
                                  (con-id c))])
                       (set! conns
                             (for/list ([c conns]
                                        #:when (con-valid? c))
                               c))
                       (package '() dis))]
                    [else (validate)
                      (package `(,(message -1 'validate)) '())])]))
    
    
    (define/public (on-msg id msg _)
      (set! conns
            (for/list ([c conns])
              (if (not (= (con-id c) id))
                  c
                  (con id #t))))
      (package '() '()))
    
    
    (define/public (on-new id)
      (set! conns (cons (con id #t) conns))
      (package '() '()))
    
    ;; req functions. not used
    (define/public (on-disconnect id)
      (package '() '()))
    (define/public (next-state-ready?) #f)
    (define/public (transition c%) (error 'transition "may not transition"))
    
    (super-new)
    (inspect #f)))

;; argument-less instantiation for above

;; makes and starts the server
(define server (make-server/effectful (new in-serv% [tenents '(pranav spencer josh cole)])
                                      #:port PORT #:hostname HOST))

;; fullscreen
(define-values (WIDTH HEIGHT) (get-display-size #t))

;; frame augmented to shutdown server on close
(define this-frame%
  (class* frame% ()
    (init label)
    (define/augment (on-close)
      (send server stop))
    (super-new [label label]
               [min-width WIDTH]
               [min-height HEIGHT]
               [style (if (debug) 
                          '()
                          '(no-system-menu no-resize-border 
                                           no-caption hide-menu-bar
                                           float))]
               [stretchable-width #f]
               [stretchable-height #f])))

;; the frame
(define window (new this-frame% [label "check-in!"]))

;; align everything vertically
(define holder (new vertical-panel% [parent window]))

;; shiny! :D
(define IN-IMG (read-bitmap "images/in.jpg" 'jpeg))
(define OUT-IMG (read-bitmap "images/out.jpg" 'jpeg))

;; buttons
(define FONT-SIZE 100) ;;FIXME might this be the resize issue?
(define FONT (make-font #:size FONT-SIZE #:smoothing 'unsmoothed #:size-in-pixels? #t))
(define LENGTH 
  (for/fold ([size 0])
    ([s (send (send server get-state) get-names)])
    (max size (string-length (symbol->string s)))))

(define check-button%
  (class button%
    (init-field name)
    
    (define/public (get-button-lable name)
      (define n (symbol->string name))
      (list IN-IMG
            (apply string-append
                   `(,n
                     ,@(for/list ([i (in-range (max (* 2 (- LENGTH (string-length n)))
                                                    1))])
                         " ")
                     ,@(for/list ([i (in-range 0 
                                               (- WIDTH 0
                                                  #;(* LENGTH FONT-SIZE)
                                                  #;(send IN-IMG get-width))
                                               FONT-SIZE)])
                         "  ")))
            'right))
    
    (define/public (get-cur-img name)
      (get-button-img (send (send server get-state) in? name)))
    
    (define/public (get-button-img in?)
      (if in?
          IN-IMG
          OUT-IMG))
    
    (define/public (click)
      (send (send server get-state) port name)
      (send this set-label (get-cur-img name)))
    
    (super-new [label (get-button-lable name)]
               [parent holder]
               [callback (λ (e b)
                           (send this click))]
               [font FONT]
               [stretchable-width #t]
               [stretchable-height #t])))

;; makes the buttons for each tenent
(define buttons (for/list ([name (send (send server get-state) get-names)])
                  (new check-button% [name name])))

;; start the server
(send server start)
(send window show #t)

(define-namespace-anchor test)
(when (debug)
  (parameterize ([current-namespace 
                  (namespace-anchor->namespace test)])
    (graphical-read-eval-print-loop)))